package org.reborn.model.bound

import org.reborn.math._

object BoundIntersection {
	
  def intersects(bound1:Bound, bound2:Bound):Boolean = {
	(bound1,bound2) match{
		//Special case where one bound is an InfiniteBound
		case (ib:InfiniteBound,b:Bound) => true;		//Infinite bound intersects everything
		case (b:Bound,ib:InfiniteBound) => true;		//Everything intersects infinite bound
		
		//First bound is BoundingSphere
		case (s1:BoundingSphere, s2:BoundingSphere) => sphereIntersectsSphere(s1,s2)
		case (s:BoundingSphere, b:AxisAlignedBoundingBox) => sphereIntersectsAabb(s,b)
		case (s:BoundingSphere, f:InfiniteFrustum) => sphereIntersectsInfiniteFrustum(s,f)
		//Sphere - Hull intersection can be handled with one of the generic cases down below
		
		//First bound is AABB
		case (b1:AxisAlignedBoundingBox,b2:AxisAlignedBoundingBox) => aabbIntersectsAabb(b1,b2)
		case (b:AxisAlignedBoundingBox,s:BoundingSphere) => sphereIntersectsAabb(s,b)
		//AABB - Hull intersection can be handled with one of the generic cases down below
		
		//First bound is convex hull
		//Hull - Hull intersection can be handled with one of the generic cases down below
		//Hull - AABB intersection can be handled with one of the generic cases down below
		//Hull - Sphere intersection can be handled with one of the generic cases down below
		
		//Generic case where One of the bounds is an InfiniteFrustum
		case (f:InfiniteFrustum, s:BoundingSphere) => sphereIntersectsInfiniteFrustum(s,f)
		case (f:InfiniteFrustum, b:ClosedPolygonalBound) => closedPolygonalBoundIntersectsInfiniteFrustum(b,f)
		case (b:ClosedPolygonalBound, f:InfiniteFrustum) => closedPolygonalBoundIntersectsInfiniteFrustum(b,f)
		
		//Generic implementation where one of the bounds is an implementation of ClosedPolygonalBound
		case (pb1:ClosedPolygonalBound,pb2:ClosedPolygonalBound) => closedPolygonalBoundIntersectsClosedPolygonalBound(pb1,pb2)
		case (s:BoundingSphere,pb:ConvexPlaneBound) => sphereIntersectsConvexPlaneBound(s,pb)
		case (pb:ConvexPlaneBound,s:BoundingSphere) => sphereIntersectsConvexPlaneBound(s,pb)
		
		//If we don't have an implementation, throw an exception
		case _ => throw new UnsupportedOperationException("Intersection test with bound1: "+bound1.getClass+" and bound2:"+bound2.getClass+" is not supported.");
	}
  }
	
  def sphereIntersectsSphere(s1:BoundingSphere,s2:BoundingSphere):Boolean = {
	s1.radius + s2.radius >= (s1.position - s2.position).magnitude
  }
  
  def aabbIntersectsAabb(b1:AxisAlignedBoundingBox,b2:AxisAlignedBoundingBox):Boolean = {
	val noIntersection = 
		b1.min(0) > b2.max(0) ||
		b1.min(1) > b2.max(1) ||
		b1.min(2) > b2.max(2) ||
		b2.min(0) > b1.max(0) ||
		b2.min(1) > b1.max(1) ||
		b2.min(2) > b1.max(2)
	return !noIntersection
  }
  
  def closedPolygonalBoundIntersectsClosedPolygonalBound(pb1:ClosedPolygonalBound,pb2:ClosedPolygonalBound):Boolean = {
	//Check a less computationally expensive test first to weed out bounds 
	//that are far from each other
	if(!pb1.aabb.intersects(pb2.aabb))
	  return false
	  
	//Check if any of the bounds's points are inside the other one's 
	for(point <- pb1.points){
      if(pb2.containsPoint(point))
    	return true;
	}
	
	//Check if any of the other bounds's points are inside the first one's 
	for(point <- pb2.points){
      if(pb1.containsPoint(point))
    	return true;
	}
	
	//Check if any of the bounds's edges intersect the other's polygons
	for(edge <- pb1.edges; plane <- pb2.planes){
	  val dist = edge.intersectsAt(plane)
	  if(dist > 0f && dist < 1f){
	 	val intPoint = edge.pointOnLine(dist)
	 	if(pb2.containsPoint(intPoint))
	 	  return true
	  }
	}
	
	//Check if any of the other bounds's edges intersect the the first one's polygons
	for(edge <- pb2.edges; plane <- pb1.planes){
	  val dist = edge.intersectsAt(plane)
	  if(dist > 0f && dist < 1f){
	 	val intPoint = edge.pointOnLine(dist)
	 	if(pb1.containsPoint(intPoint))
	 	  return true
	  }
	}
	
	return false;
  }
  
  def sphereIntersectsConvexPlaneBound(s:BoundingSphere,pb:ConvexPlaneBound):Boolean = {
	val distToPlanes = pb.planes.map((plane) => plane.dist(s.position))
	
	//If the distance between the sphere and any of the planes is positive
	//it means there is no intersection
	if(distToPlanes.exists(_ - s.radius > 0f))
	  return false;
	
	//If the sphere is completely inside the bound, then it intersects
	if(distToPlanes.forall((dist) => dist <= 0 && dist + s.radius <= 0f))
	  return true;
	
	//If the sphere has 1 and only 1 plane that it intersects from the
	//positive side, then the bound is intersected
	if(distToPlanes.map((dist) => if(dist >= 0) 1 else 0).reduceLeft(_+_) == 1)
	  return true;
	
	//if any of the edges in the PolygonalBound intersect the sphere
	//then both bounds intersect
	for(edge <- pb.edges){
	  //transform edge into sphere's coordinate space
	  val intersectPoints = edge.intersectsSphereAt(s.position,s.radius) 
	  intersectPoints.foreach{(dist) =>
	 	if(0f <= dist && dist <= 1f)
	 	  return true;
	  }
	}
	
	//If no planes points or lines intersected, then the bounds did not intersect 
	return false;
  }
  
  def sphereIntersectsAabb(s:BoundingSphere,b:AxisAlignedBoundingBox):Boolean = {
	var dist = 0f
	
	//find the square of the distance
    //from the sphere to the box
    for(i <- 0 until 3){
      if( s.position(i) < b.min(i) ){
        val sDist = s.position(i) - b.min(i);
        dist += sDist*sDist;
      }
      else if( s.position(i) > b.max(i) ){
        val sDist = s.position(i) - b.max(i);
        dist += sDist*sDist;
      }
    }
	
	return dist <= s.radiusSquared; 
  }
  
  /**
   * Not a 100% accurate bound test. It may produce false positives (test returns an intersection
   * when there actually wasn't one), but it is guaranteed not to produce false negatives (test returns
   * no intersection when there actually was one).
   */
  def sphereIntersectsInfiniteFrustum(s:BoundingSphere,f:InfiniteFrustum):Boolean = {
	//If distance to any plane is positive then there is no intersection
	f.planes.foreach{(plane)=>
	  val dist = plane.dist(s.position)
	  if(dist - s.radius > 0)
		return false
	}
	
	return true
  }
  
  /**
   * Not a 100% accurate bound test. It may produce false positives (test returns an intersection
   * when there actually wasn't one), but it is guaranteed not to produce false negatives (test returns
   * no intersection when there actually was one).
   */
  def closedPolygonalBoundIntersectsInfiniteFrustum(b:ClosedPolygonalBound,f:InfiniteFrustum):Boolean = {
	
	//If all points on the box are on the positive side of any of the planes
	//then there is no intersection, otherwise there is
	f.planes.foreach{(plane)=>
	  if(b.points.forall(plane.dist(_) > 0f))
	 	return false
	}
	
	return true
  }
  
}